{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "1. 根据LFM的embedding生成输入\n",
    "2. 将数据输入到FM进行训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-09-26T04:52:55.319678Z",
     "start_time": "2020-09-26T04:52:42.048146Z"
    }
   },
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'torch' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-4-7aa9a8a46430>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     32\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mastype\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfloat32\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     33\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 34\u001b[1;33m X_tensor,  y_tensor = torch.from_numpy(X).to(device), torch.from_numpy(\n\u001b[0m\u001b[0;32m     35\u001b[0m     y).to(device)\n\u001b[0;32m     36\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mNameError\u001b[0m: name 'torch' is not defined"
     ]
    }
   ],
   "source": [
    "# 处理数据，生成输入\n",
    "import pandas as pd\n",
    "from lfm_new_data import LFM\n",
    "import numpy as np\n",
    "model=pd.read_pickle('UI.pkl')\n",
    "user_embeddings=model.user_embeddings.weight.data.numpy()\n",
    "item_embeddings=model.item_embeddings.weight.data.numpy()\n",
    "\n",
    "data_path=\"hin/data/u.data\"\n",
    "data_fields = ['user_id', 'item_id', 'rating', 'timestamp']\n",
    "# all data file\n",
    "data_df = pd.read_table(data_path, names=data_fields)\n",
    "\n",
    "# get user number\n",
    "n_users = max(data_df['user_id'].values)\n",
    "# get item number\n",
    "n_items = max(data_df['item_id'].values)\n",
    "\n",
    "data_df['user_id']-=1\n",
    "data_df['item_id']-=1\n",
    "\n",
    "\n",
    "X=[]\n",
    "for i in range(data_df.shape[0]):\n",
    "    x=data_df.iloc[i]\n",
    "    X.append(np.concatenate((user_embeddings[x['user_id']],item_embeddings[x['item_id']])))\n",
    "    \n",
    "X=np.array(X)\n",
    "\n",
    "y=data_df.rating.values\n",
    "y=y.reshape(-1,1)\n",
    "y=y.astype(np.float32)\n",
    "\n",
    "X_tensor,  y_tensor = torch.from_numpy(X).to(device), torch.from_numpy(\n",
    "    y).to(device)\n",
    "\n",
    "train_num = int(X_tensor.shape[0] * 0.9)\n",
    "train_set = TensorDataset(X_tensor[:train_num], y_tensor[:train_num])\n",
    "valid_set = TensorDataset(X_tensor[train_num:], y_tensor[train_num:])\n",
    "\n",
    "loaders = {'train': DataLoader(train_set, batch_size=batch_size, shuffle=True),\n",
    "           'valid': DataLoader(valid_set, batch_size=batch_size, shuffle=False)}\n",
    "\n",
    "input_size = X_tensor.shape[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-09-26T06:14:42.813746Z",
     "start_time": "2020-09-26T06:14:42.701042Z"
    }
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "pd.to_pickle(loaders,'loaders.pkl')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-09-26T04:59:31.932110Z",
     "start_time": "2020-09-26T04:59:31.802249Z"
    }
   },
   "outputs": [],
   "source": [
    "import random\n",
    "import math\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import math\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.utils.data as data\n",
    "from tqdm import tqdm\n",
    "from utils import Interactions\n",
    "import os\n",
    "np.random.seed(1024)\n",
    "from torch.utils.data import TensorDataset, DataLoader\n",
    "from sklearn.metrics import roc_auc_score\n",
    "# for reproducibility\n",
    "def seed_everything(seed=1234):\n",
    "    random.seed(seed)\n",
    "    os.environ['PYTHONHASHSEED'] = str(seed)\n",
    "    np.random.seed(seed)\n",
    "    torch.manual_seed(seed)\n",
    "    torch.cuda.manual_seed(seed)\n",
    "    torch.backends.cudnn.deterministic = True\n",
    "seed_everything()\n",
    "\n",
    "# To compute probalities\n",
    "def sigmoid(x):\n",
    "    return 1 / (1 + np.exp(-x))\n",
    "\n",
    "class FMG(torch.nn.Module):\n",
    "    def __init__(self, input_size=2, n_factors=10, lr=0.01, weight_decay=0.):\n",
    "        super(FMG, self).__init__()\n",
    "\n",
    "        # get factor number\n",
    "        # Initially we fill V with random values sampled from Gaussian distribution\n",
    "        # NB: use nn.Parameter to compute gradients\n",
    "        self.V = nn.Parameter(torch.randn(input_size, n_factors),requires_grad=True)\n",
    "        self.lin = nn.Linear(input_size, 1)\n",
    "\n",
    "        self.optimizer = torch.optim.Adam(self.parameters(),\n",
    "                                   lr=lr, weight_decay=weight_decay)\n",
    "\n",
    "    def forward(self, x):\n",
    "        out_1 = torch.matmul(x, self.V).pow(2).sum(1, keepdim=True)  # S_1^2\n",
    "        out_2 = torch.matmul(x.pow(2), self.V.pow(2)).sum(1, keepdim=True)  # S_2\n",
    "\n",
    "        out_inter = 0.5 * (out_1 - out_2)\n",
    "        out_lin = self.lin(x)\n",
    "        out = out_inter + out_lin\n",
    "\n",
    "        return out\n",
    "\n",
    "    def fit(self, loaders, epochs=5):\n",
    "        # training cycle\n",
    "        best_score = 0.\n",
    "        for epoch in range(epochs):\n",
    "            losses = {'train': 0., 'valid': 0}\n",
    "\n",
    "            for phase in ['train', 'valid']:\n",
    "\n",
    "                if phase == 'train':\n",
    "                    self.train()\n",
    "                else:\n",
    "                    self.eval()\n",
    "\n",
    "                for batch_x, batch_y in loaders[phase]:\n",
    "                    self.optimizer.zero_grad()\n",
    "                    out = self.forward(batch_x)\n",
    "#                     loss = nn.BCEWithLogitsLoss()(out, batch_y)\n",
    "                    loss = nn.MSELoss(reduction='sum')(out, batch_y)\n",
    "                    losses[phase] += loss.item() * batch_x.size(0)\n",
    "\n",
    "                    with torch.set_grad_enabled(phase == 'train'):\n",
    "                        if phase == 'train':\n",
    "                            loss.backward()\n",
    "                            #                             scheduler.step()\n",
    "                            self.optimizer.step()\n",
    "\n",
    "                losses[phase] /= len(loaders[phase].dataset)\n",
    "\n",
    "            # after each epoch check if we improved roc auc and if yes - save model\n",
    "            with torch.no_grad():\n",
    "                model.eval()\n",
    "\n",
    "                y_pred,y_true = [],[]\n",
    "                for batch_x, batch_y in loaders['valid']:\n",
    "                    out = self.forward(batch_x)\n",
    "                    preds = sigmoid(out.cpu().numpy())\n",
    "                    y_pred += preds.tolist()\n",
    "                    y_true += batch_y.tolist()\n",
    "#                 epoch_score = roc_auc_score(np.array(y_true), np.array(y_pred))\n",
    "#                 embed()\n",
    "                epoch_score=sum([(y[0] - x[0]) ** 2 for x, y in zip(y_true, y_pred)]) / len(y_pred)\n",
    "\n",
    "\n",
    "            if ((epoch + 1) % 1) == 0:\n",
    "                print(\n",
    "                    f'epoch {epoch + 1} train loss: {losses[\"train\"]:.3f} valid loss {losses[\"valid\"]:.3f} valid mse {epoch_score:.3f}')\n",
    "        return\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-09-19T12:03:54.329729Z",
     "start_time": "2020-09-19T12:03:54.324743Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'train': <torch.utils.data.dataloader.DataLoader at 0x21f0edae390>,\n",
       " 'valid': <torch.utils.data.dataloader.DataLoader at 0x21f0edae128>}"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "loaders"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-09-26T05:00:24.442486Z",
     "start_time": "2020-09-26T04:59:35.004684Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 1 train loss: 179.141 valid loss 32.662 valid mse 7.833\n",
      "epoch 2 train loss: 30.039 valid loss 27.462 valid mse 7.818\n",
      "epoch 3 train loss: 27.764 valid loss 30.055 valid mse 7.858\n",
      "epoch 4 train loss: 26.936 valid loss 27.466 valid mse 7.819\n",
      "epoch 5 train loss: 26.829 valid loss 27.343 valid mse 7.798\n",
      "epoch 6 train loss: 26.917 valid loss 25.968 valid mse 7.807\n",
      "epoch 7 train loss: 26.732 valid loss 27.032 valid mse 7.805\n",
      "epoch 8 train loss: 26.787 valid loss 26.377 valid mse 7.811\n",
      "epoch 9 train loss: 26.888 valid loss 26.035 valid mse 7.818\n",
      "epoch 10 train loss: 26.736 valid loss 26.214 valid mse 7.816\n",
      "epoch 11 train loss: 26.753 valid loss 26.125 valid mse 7.812\n",
      "epoch 12 train loss: 26.949 valid loss 25.893 valid mse 7.816\n",
      "epoch 13 train loss: 26.837 valid loss 27.020 valid mse 7.830\n",
      "epoch 14 train loss: 26.805 valid loss 26.078 valid mse 7.820\n",
      "epoch 15 train loss: 26.763 valid loss 27.150 valid mse 7.806\n"
     ]
    }
   ],
   "source": [
    "model = FMG(input_size)\n",
    "model.fit(loaders,15)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
